﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace OSGeo.MapGuide
{
    /// <summary>
    /// Represents a feature data access interface to a single feature source
    /// </summary>
    public abstract class MgFeatureRepository
    {
        protected MgFeatureService _service;
        protected MgResourceIdentifier _featureSourceId;
        protected MgFeatureCommandCollection _commandQueue;

        protected MgFeatureRepository(string featureSourceId, MgFeatureService service)
            : this(new MgResourceIdentifier(featureSourceId), service)
        {
        }

        protected MgFeatureRepository(MgResourceIdentifier featureSourceId, MgFeatureService service)
        {
            _service = service;
            _featureSourceId = featureSourceId;
            _commandQueue = new MgFeatureCommandCollection();
        }

        protected abstract string GetSchemaName();

        protected abstract string GetClassName();

        protected MgClassDefinition _class;

        public MgClassDefinition GetClassDefinition()
        {
            if (_class == null)
            {
                _class = _service.GetClassDefinition(_featureSourceId, GetSchemaName(), GetClassName());
            }
            return _class;
        }

        public string GetQualifiedClassName() { return GetSchemaName() + ":" + GetClassName(); }

        /// <summary>
        /// Inserts a single feature
        /// </summary>
        /// <param name="feature"></param>
        /// <param name="useTransaction"></param>
        /// <returns></returns>
        public MgInsertResult InsertFeature(MgPropertyCollection feature, bool useTransaction)
        {
            try
            {
                _commandQueue.Clear();
                MgInsertFeatures insert = new MgInsertFeatures(GetQualifiedClassName(), feature);
                _commandQueue.Add(insert);
                MgPropertyCollection result = _service.UpdateFeatures(_featureSourceId, _commandQueue, useTransaction);
                if (result[0].PropertyType == MgPropertyType.String) //Error
                    return MgInsertResult.Fail((result[0] as MgStringProperty).Value);
                else
                    return MgInsertResult.Ok;
            }
            catch
            {
                throw;
            }
            finally
            {
                _commandQueue.Clear();
            }
        }

        /// <summary>
        /// Inserts a batch of features.
        /// </summary>
        /// <param name="features"></param>
        /// <param name="useTransaction"></param>
        /// <returns></returns>
        public MgInsertResult[] InsertBatch(MgBatchPropertyCollection features, bool useTransaction)
        {
            try
            {
                int batchSize = 1;
                _commandQueue.Clear();
                List<MgInsertResult> insertResults = new List<MgInsertResult>();
                if (batchSize > 1)
                {
                    //We probably shouldn't take this path, as Trac #649 means a partial update may 
                    //occur on a non-transactional feature source. We want to capture each insertion failure
                    MgBatchPropertyCollection insertBatch = new MgBatchPropertyCollection();
                    for (int i = 1; i <= features.Count; i++)
                    {
                        insertBatch.Add(features[i - 1]);
                        if (i % batchSize == 0) //We're at batchsize
                        {
                            try
                            {
                                MgInsertFeatures insert = new MgInsertFeatures(GetQualifiedClassName(), insertBatch);
                                _commandQueue.Add(insert);
                                MgPropertyCollection result = _service.UpdateFeatures(_featureSourceId, _commandQueue, useTransaction);
                                if (result[0].PropertyType == MgPropertyType.String) //Error
                                    insertResults.Add(MgInsertResult.Fail((result[0] as MgStringProperty).Value));
                                else
                                    insertResults.Add(MgInsertResult.Ok);
                            }
                            finally
                            {
                                insertBatch.Clear(); //reset insert batch
                                _commandQueue.Clear();
                            }
                        }
                    }
                }
                else //One-by-one
                {
                    foreach (MgPropertyCollection feature in features)
                    {
                        MgInsertFeatures insert = new MgInsertFeatures(GetQualifiedClassName(), feature);
                        _commandQueue.Add(insert);
                    }
                    MgPropertyCollection result = _service.UpdateFeatures(_featureSourceId, _commandQueue, useTransaction);
                    foreach (MgProperty res in result)
                    {
                        if (res.PropertyType == MgPropertyType.String) //Error
                            insertResults.Add(MgInsertResult.Fail((res as MgStringProperty).Value));
                        else
                            insertResults.Add(MgInsertResult.Ok);
                    }
                }
                return insertResults.ToArray();
            }
            catch
            {
                throw;
            }
            finally
            {
                _commandQueue.Clear();
            }
        }

        /// <summary>
        /// Updates features in this feature source
        /// </summary>
        /// <param name="props"></param>
        /// <param name="updateFilter"></param>
        /// <param name="useTransaction"></param>
        /// <returns></returns>
        public int UpdateFeatures(MgPropertyCollection props, string updateFilter, bool useTransaction)
        {
            try
            {
                _commandQueue.Clear();
                MgUpdateFeatures update = new MgUpdateFeatures(GetQualifiedClassName(), props, updateFilter);
                _commandQueue.Add(update);
                MgPropertyCollection result = _service.UpdateFeatures(_featureSourceId, _commandQueue, useTransaction);
                if (result[0].PropertyType == MgPropertyType.String) //Error
                    throw new ApplicationException((result[0] as MgStringProperty).Value);
                else
                    return (result[0] as MgInt32Property).Value; //features affected
            }
            catch
            {
                throw;
            }
            finally
            {
                _commandQueue.Clear();
            }
        }

        /// <summary>
        /// Deletes features from this feature source
        /// </summary>
        /// <param name="deleteFilter"></param>
        /// <param name="useTransaction"></param>
        /// <returns></returns>
        public int DeleteFeatures(string deleteFilter, bool useTransaction)
        {
            try
            {
                _commandQueue.Clear();
                MgDeleteFeatures delete = new MgDeleteFeatures(GetQualifiedClassName(), deleteFilter);
                _commandQueue.Add(delete);
                MgPropertyCollection result = _service.UpdateFeatures(_featureSourceId, _commandQueue, useTransaction);
                if (result[0].PropertyType == MgPropertyType.String) //Error
                    throw new ApplicationException((result[0] as MgStringProperty).Value);
                else
                    return (result[0] as MgInt32Property).Value; //features affected
            }
            catch
            {
                throw;
            }
            finally
            {
                _commandQueue.Clear();
            }
        }

        /// <summary>
        /// Selects features from this feature source. Each result is "streamed" to the caller.
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="propertyNames"></param>
        /// <param name="computedProperties"></param>
        /// <returns></returns>
        public IEnumerable<MgPropertyCollection> SelectFeaturesStreamed(string filter, string[] propertyNames, MgStringPair[] computedProperties)
        {
            MgFeatureQueryOptions query = new MgFeatureQueryOptions();
            if (!string.IsNullOrEmpty(filter))
            {
                query.SetFilter(filter);
            }
            if (null != propertyNames && propertyNames.Length > 0)
            {
                foreach (string p in propertyNames)
                {
                    query.AddFeatureProperty(p);
                }
            }
            if (null != computedProperties && computedProperties.Length > 0)
            {
                foreach (MgStringPair p in computedProperties)
                {
                    query.AddComputedProperty(p.Key, p.Value);
                }
            }
            MgFeatureReader reader = _service.SelectFeatures(_featureSourceId, GetQualifiedClassName(), query);
            MgClassDefinition cls = reader.GetClassDefinition();
            string[] propNames = cls.GetPropertyNames();
            while (reader.ReadNext())
            {
                MgPropertyCollection res = reader.ReadFeature(propNames);
                yield return res;
            }
            reader.Close();
        }

        /// <summary>
        /// Selects features from this feature source
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="propertyNames"></param>
        /// <param name="computedProperties"></param>
        /// <returns></returns>
        public MgBatchPropertyCollection SelectFeatures(string filter, string[] propertyNames, MgStringPair[] computedProperties)
        {
            MgBatchPropertyCollection results = new MgBatchPropertyCollection();
            MgFeatureQueryOptions query = new MgFeatureQueryOptions();
            if (!string.IsNullOrEmpty(filter))
            {
                query.SetFilter(filter);
            }
            if (null != propertyNames && propertyNames.Length > 0)
            {
                foreach (string p in propertyNames)
                {
                    query.AddFeatureProperty(p);
                }
            }
            if (null != computedProperties && computedProperties.Length > 0)
            {
                foreach (MgStringPair p in computedProperties)
                {
                    query.AddComputedProperty(p.Key, p.Value);
                }
            }
            MgFeatureReader reader = _service.SelectFeatures(_featureSourceId, GetQualifiedClassName(), query);
            MgClassDefinition cls = reader.GetClassDefinition();
            string[] propNames = cls.GetPropertyNames();
            while (reader.ReadNext())
            {
                MgPropertyCollection res = reader.ReadFeature(propNames);
                results.Add(res);
            }
            reader.Close();
            return results;
        }
    }
}
